/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

 ****/
package org.activequant.util.charting;

import java.awt.Color;
import java.awt.Font;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;

import org.activequant.core.types.TimeStamp;
import org.activequant.core.types.Tuple;
import org.apache.log4j.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.SegmentedTimeline;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.TimeSeriesDataItem;

/**
 * 
 * Chart base class, based on JFreeChart<br>
 * Holds the following associated variables:
 * <ul>
 * <li>chart(JFreeChart)</li>
 * <li>dataset(TimeSeriesCollection)</li>
 * <li>drawColor(Color)</li>
 * </ul>
 * <b>History:</b><br> 
 * - [14.08.2007] Created (Ulrich Staudinger)<br> 
 * - [29.09.2007] Removed warnings (Erik Nijkamp)<br>
 * - [31.10.2007] Cleanup and adding methods from derived classes (Ulrich Staudinger)<br>
 * 
 * @author ustaudinger
 */
public class Chart {

	protected final static Logger log = Logger.getLogger(Chart.class);
	
	/**
	 * protected JFreeChart chart;<br/>
	 * the underlying chart.
	 */
	protected JFreeChart chart;
	/**
	 * protected List&lt;TimeSeries&gt; datasets = new ArrayList&lt;TimeSeries&gt;();<br/>
	 * counter storing the amount of current data sets in use.
	 */
	protected List<TimeSeries> datasets = new ArrayList<TimeSeries>();
	/**
	 * Color drawColor = null; 
	 */
	Color drawColor = null; 
	
	/**
	 * plain constructor.<br/>
	 * Constructs a Chart whose associated chart(JFreeChart) is a time series chart.<br/>
	 * A time series chart is an {@link XYPlot} with a {@link DateAxis} for the x-axis and a  {@link NumberAxis} for the y-axis.  The default renderer is an {@link XYLineAndShapeRenderer}. <P>
     * A convenient dataset to use with this chart is a  {@link org.jfree.data.time.TimeSeriesCollection}.<br/>
     * The associated chart(JFreeChart) has no title(String), empty timeAxisLabel(String) and valueAxisLabel(String), has no dataset set, a legend is required but no tooltips and URLs.
	 */
	public Chart() {
		chart = ChartFactory.createTimeSeriesChart(null, "", "", null, true, false, false);
		
	}
	
	/**
	 * overloaded constructor. <br/>
	 * Constructs a Chart whose associated chart(JFreeChart) is a time series chart.<br/>
	 * A time series chart is an {@link XYPlot} with a {@link DateAxis} for the x-axis and a  {@link NumberAxis} for the y-axis.  The default renderer is an {@link XYLineAndShapeRenderer}. <P>
     * A convenient dataset to use with this chart is a  {@link org.jfree.data.time.TimeSeriesCollection}.<br/>
     * The associated chart(JFreeChart) has no title(String), uses the <strong>given timeAxisLabel(String) and valueAxisLabel(String)</strong>, has no dataset set, a legend is required but no tooltips and URLs.
	 * @param timeAxisLabel
	 * @param valueAxisLabel
	 */
	public Chart(String timeAxisLabel, String valueAxisLabel){
		chart = ChartFactory.createTimeSeriesChart(null, timeAxisLabel, valueAxisLabel, null, true, false, false);
		
	}

	/**
	 * method to add a line series chart to the current chart. <br/>
	 * Adds a TimeSeries Line Chart with the given title(String) to the XYPlot of the associated chart(JFreeChart) using the given dateAndValues(List&lt;Tuple&lt;TimeStamp, Double&gt;&gt;) as its data items:<br/>
	 * -Creates a ts(TimeSeries) with the given title(String) and adds a TimeSeriesDataItem to it for each of the given dateAndValues(List&lt;Tuple&lt;TimeStamp, Double&gt;&gt;) 
	 * pairs using Millisecond resolution/time period.<br/>
	 * -Adds that ts(TimeSeries) to the associated datasets(List&lt;TimeSeries&gt;).<br/>
	 * -Creates a dataset(TimeSeriesCollection) holding that one ts(TimeSeries).<br/>
	 * -Gets plot1(XYPlot) as the XYPlot of the associated chart(JFreeChart)<br/>
	 * -Sets the dataset at the <code>datasets.size()</code> index of the plot1(XYPlot) with the dataset(TimeSeriesCollection) and sets its
	 * renderer with a new XYLineAndShapeRenderer which renders no shapes, no outlines, and if set, uses the associated drawColor(Color) to paint the lines.
	 * @param title
	 * @param dateAndValues
	 */
	public void addLineSeriesChart(String title, List<Tuple<TimeStamp, Double>> dateAndValues) {
		
		// creating a new jfree chart time series object. 
		final TimeSeries ts = new TimeSeries(title, Millisecond.class);
		
		
		// iterate over the incoming value tuples and add them.  
		for (Tuple<TimeStamp, Double> tuple : dateAndValues) {
			TimeSeriesDataItem item = new TimeSeriesDataItem(new Millisecond(tuple.getObject1().getDate()), tuple.getObject2());
			ts.addOrUpdate(item.getPeriod(), item.getValue());
		}

		datasets.add(ts);
		
		// 
		final TimeSeriesCollection dataset = new TimeSeriesCollection(ts);
		
		// add it to the chart plot. 
		final XYPlot plot1 = chart.getXYPlot();

		// disable all shape rendering. 
		final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
		renderer.setDrawOutlines(false);
		renderer.setUseOutlinePaint(false);
		renderer.setShapesVisible(false);
		if(drawColor!=null)renderer.setSeriesPaint(0, drawColor);
		
		// finally add the data set to chart 
		plot1.setDataset(datasets.size(), dataset);
		plot1.setRenderer(datasets.size(), renderer);
		
		
		
		
	}
	
	/**
	 * Adds or updates a point in the dataset.<br/>
	 * Gets the ts(TimeSeries) found at the given datasetIndex(int) index in the associated datasets(List&lt;TimeSeries&gt;).<br/>
	 * Adds/updates the value of that ts(TimeSeries) at the given timeStamp(TimeStamp) point with the given value(double)
	 * @param datasetIndex dataset index (zero-based).
	 * @param timeStamp time stamp of the event.
	 * @param value event value.
	 */
	public void addSeriesDataItem(int datasetIndex, TimeStamp timeStamp, double value) {
		if(datasetIndex >= datasets.size()) {
			throw new IllegalArgumentException("wrong dataset index: size=" + datasets.size() + ", index=" + datasetIndex);
		}
		TimeSeries ts = datasets.get(datasetIndex);
		ts.addOrUpdate(new Millisecond(timeStamp.getDate()), value);
	}
	
	/**
	 * Deletes data points that are older than given time stamp from <em>all datasets</em>.
	 * This allows to keep sliding window of the data with the given width.<br/>
	 * Iterates the associated datasets(List&lt;TimeSeries&gt;) and for each ts(TimeSeries) whose index(int) (corresponding to the given timeStamp(TimeStamp)) is 0 or greater,
	 * deletes all its data from 0 to that index(int)
	 *  
	 * @param timeStamp truncation threshold.
	 */
	public void deleteBefore(TimeStamp timeStamp) {
		for(TimeSeries ts : datasets) {
			int index = ts.getIndex(new Millisecond(timeStamp.getDate()));
			if(index >= 0) {
				ts.delete(0, index);
			}
		}
	}

	/**
	 * Deletes data points from a given dataset which are older than the time stamp.
	 * This allows to keep sliding window of the data with the given width.<br/>
	 * Gets the ts(TimeSeries) found at the given datasetIndex(int) index in the associated datasets(List&lt;TimeSeries&gt;).<br/>
	 * If the ts(TimeSeries) has any data points older than the given timeStamp(TimeStamp), it just deletes those points.
	 * @param datasetIndex index of the dataset to truncate.
	 * 
	 * @param timeStamp truncation threshold.
	 */
	public void deleteBefore(int datasetIndex, TimeStamp timeStamp) {
		if(datasetIndex >= datasets.size()) {
			throw new IllegalArgumentException("wrong dataset index: size=" + datasets.size() + ", index=" + datasetIndex);
		}
		TimeSeries ts = datasets.get(datasetIndex);
		int index = ts.getIndex(new Millisecond(timeStamp.getDate()));
		if(index >= 0) {
			ts.delete(0, index);
		}
	}

	/**
	 * method to add a line series drawing to this chart.<br/>
	 * Adds a TimeSeries Line Chart with the given title(String) to the XYPlot of the associated chart(JFreeChart) using the given dates(TimeStamp[]) and values(Double[]) as its data items:<br/>
	 * -Creates a ts(TimeSeries) with the given title(String) and adds a TimeSeriesDataItem to it for each of the given date(TimeStamp)-value(Double) pairs using Millisecond resolution/time period.<br/>
	 * -Adds that ts(TimeSeries) to the associated datasets(List&lt;TimeSeries&gt;).<br/>
	 * -Creates a dataset(TimeSeriesCollection) holding that one ts(TimeSeries).<br/>
	 * -Gets plot1(XYPlot) as the XYPlot of the associated chart(JFreeChart)<br/>
	 * -Sets the dataset at the <code>datasets.size()</code> index of the plot1(XYPlot) with the dataset(TimeSeriesCollection) and sets its
	 * renderer with a new XYLineAndShapeRenderer which renders no shapes, no outlines, and if set, uses the associated drawColor(Color) to paint the lines.
	 * @param title
	 * @param dates
	 * @param values
	 */
	public void addLineSeriesChart(String title, TimeStamp[] dates, Double[] values){
		assert(dates.length == values.length);
		final List<Tuple<TimeStamp,Double>> vals = new ArrayList<Tuple<TimeStamp,Double>>();
		for(int i=0;i<dates.length;i++){
			Tuple<TimeStamp, Double> tuple = new Tuple<TimeStamp, Double>(dates[i], values[i]);
			vals.add(tuple);
		}
		this.addLineSeriesChart(title, vals);
	}
	
	/**
	 * method to add a line series drawing to this chart, just with a different parameter type. Same functionality as above.<br/>
	 * Adds a TimeSeries Line Chart with the given title(String) to the XYPlot of the associated chart(JFreeChart) using the given dates(TimeStamp[]) and values(double[]) as its data items:<br/>
	 * -Creates a ts(TimeSeries) with the given title(String) and adds a TimeSeriesDataItem to it for each of the given date(TimeStamp)-value(double) pairs using Millisecond resolution/time period.<br/>
	 * -Adds that ts(TimeSeries) to the associated datasets(List&lt;TimeSeries&gt;).<br/>
	 * -Creates a dataset(TimeSeriesCollection) holding that one ts(TimeSeries).<br/>
	 * -Gets plot1(XYPlot) as the XYPlot of the associated chart(JFreeChart)<br/>
	 * -Sets the dataset at the <code>datasets.size()</code> index of the plot1(XYPlot) with the dataset(TimeSeriesCollection) and sets its
	 * renderer with a new XYLineAndShapeRenderer which renders no shapes, no outlines, and if set, uses the associated drawColor(Color) to paint the lines.
	 * @param title
	 * @param dates
	 * @param values
	 */
	public void addLineSeriesChart(String title, TimeStamp[] dates, double[] values){
		assert(dates.length == values.length);
		final List<Tuple<TimeStamp,Double>> vals = new ArrayList<Tuple<TimeStamp,Double>>();
		for(int i=0;i<dates.length;i++){
			Tuple<TimeStamp, Double> tuple = new Tuple<TimeStamp, Double>(dates[i], values[i]);
			vals.add(tuple);
		}
		this.addLineSeriesChart(title, vals);
		
	}
	
	/**
	 * use this to add a text annotation to this chart. <br/>
	 * Creates an XYTextAnnotation with the given text(String) and and adds it at the given date(TimeStamp), yValue(double) coordinates of the XYPlot of the associated chart(JFreeChart)
	 * @param date
	 * @param yValue the vertical position of the text
	 * @param text
	 */
	public void addTextAnnotation(TimeStamp date, double yValue, String text) {
		chart.getXYPlot().addAnnotation(new XYTextAnnotation(text, date.getDate().getTime(), yValue));
	}

	/**
	 * method to add a dot chart.<br/>
	 * Adds a TimeSeries Dot Chart with the given title(String) to the XYPlot of the associated chart(JFreeChart) using the given dateAndValues(List&lt;Tuple&lt;TimeStamp, Double&gt;&gt;) as its data items:<br/>
	 * -Doesn't do anything if the associated chart(JFreeChart) is null.<br/>
	 * -Otherwise, it creates a ts(TimeSeries) with the given title(String) and adds a TimeSeriesDataItem to it for each of the given dateAndValues(List&lt;Tuple&lt;TimeStamp, Double&gt;&gt;) 
	 * pairs using Millisecond resolution/time period.<br/>
	 * -Adds that ts(TimeSeries) to the associated datasets(List&lt;TimeSeries&gt;).<br/>
	 * -Creates a dataset(TimeSeriesCollection) holding that one ts(TimeSeries).<br/>
	 * -Gets plot1(XYPlot) as the XYPlot of the associated chart(JFreeChart)<br/>
	 * -Sets the dataset at the <code>datasets.size()</code> index of the plot1(XYPlot) with the dataset(TimeSeriesCollection) and sets its
	 * renderer with a new XYLineAndShapeRenderer which renders shapes and no lines.
	 * @param title
	 * @param dateAndValues
	 */
	public void addDotSeriesChart(String title, List<Tuple<TimeStamp, Double>> dateAndValues) {

		if (chart != null) {
			//
			final TimeSeries ts = new TimeSeries(title, Millisecond.class);
			for (Tuple<TimeStamp, Double> tuple : dateAndValues) {
				//
				TimeSeriesDataItem item = new TimeSeriesDataItem(new Millisecond(tuple.getObject1().getDate()), tuple.getObject2());
				ts.addOrUpdate(item.getPeriod(), item.getValue());
			}

			datasets.add(ts);
			final TimeSeriesCollection dataset = new TimeSeriesCollection(ts);

			final XYPlot plot1 = chart.getXYPlot();

			final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
			renderer.setShapesVisible(true);
			renderer.setLinesVisible(false);
			plot1.setDataset(datasets.size(), dataset);
			plot1.setRenderer(datasets.size(), renderer);
		}
	}

	/**
	 * Persists this chart to a png file. <br/>
	 * Writes the associated chart(JFreeChart) as a PNG file at the given filename(String) location, using the given w(int) and h(int) as its width and height.
	 * created on 07.05.2006
	 * 
	 * @param filename
	 * @param w
	 * @param h
	 * @throws Exception
	 */
	public void renderToPng(String filename, int w, int h) throws Exception {
		FileOutputStream f = new FileOutputStream(filename);
		ChartUtilities.writeChartAsPNG(f, chart, w, h);
		f.close();
	}
	
	/**
	 * Creates a JFrame that can be displayed on the screen. The returned JFrame
	 * is not yet visible. To actually show it, do the following:
	 * <ul>
	 *   <li><code>setBounds()</code> to set frame bounds.
	 *   <li><code>setVisible(true)</code> to show it.
	 * </ul> 
	 * Creates and returns a ChartFrame holding the associated chart(JFreeChart), with the given title(String) and using the given scrollPane(boolean) to determine whether 
	 * to put the Chart(Panel) into a JScrollPane.
	 * @param title frame title.
	 * @param scrollPane true is scroll pane is needed.
	 * 
	 * @return JFrame
	 */
	public JFrame getJFrame(String title, boolean scrollPane) {
		return new ChartFrame(title, this.getChart(), scrollPane);
	}

	/**
	 * Creates a JFrame that can be displayed on the screen. The returned JFrame
	 * is not yet visible. To actually show it, do the following:
	 * <ul>
	 *   <li><code>setBounds()</code> to set frame bounds.
	 *   <li><code>setVisible(true)</code> to show it.
	 * </ul> 
	 * Creates and returns a ChartFrame holding the associated chart(JFreeChart), with the given title(String) and doesn't
	 * put the Chart(Panel) into a JScrollPane.
	 * @param title frame title.
	 * 
	 * @return JFrame
	 */
	public JFrame getJFrame(String title) {
		return getJFrame(title, false);
	}

	/**
	 * helper method to indicate a monday to friday timeline. <br/>
	 * <strong>1.</strong> Creates a Monday through Friday timeline(SegmentedTimeline)<br/>
	 * <strong>2.</strong> Gets the axis(DateAxis) of the XYPlot of the associated chart(JFreeChart) and sets its underlying timeline with that Monday through Friday timeline(SegmentedTimeline).<br/>
	 * The axis(DateAxis) range is automatically adjusted to fit the data, and notifies registered listeners that the axis has been modified.<br/>
	 */
	public void setSuppressWeekends() {
		SegmentedTimeline timeline = SegmentedTimeline.newMondayThroughFridayTimeline();
		final DateAxis axis = (DateAxis) chart.getXYPlot().getDomainAxis();
		
		axis.setAutoRange(true);
		axis.setTimeline(timeline);
	}
	
	/**
	 * Creates and returns an (">") upArrow(XYTextAnnotation) at the given x(double) and y(double) coordinates.
	 * @param x
	 * @param y
	 * @return
	 */
	public XYTextAnnotation getUpArrow(double x, double y){
		XYTextAnnotation arrow = new XYTextAnnotation(">", x,y);
		arrow.setRotationAngle(Math.PI * 1.5);
		arrow.setFont(new Font("SansSerif", Font.BOLD, 9));
        return arrow;
	}
	/**
	 * Creates and returns an (">") downArrow(XYTextAnnotation) at the given x(double) and y(double) coordinates.
	 * @param x
	 * @param y
	 * @return
	 */
	public XYTextAnnotation getDownArrow(double x, double y){
		XYTextAnnotation arrow = new XYTextAnnotation(">", x,y);
		arrow.setRotationAngle(Math.PI * 0.5);
		arrow.setFont(new Font("SansSerif", Font.BOLD, 9));
        return arrow;
	}
	/**
	 * returns the associated chart(JFreeChart)
	 * @return
	 */
	public JFreeChart getChart() {
		return chart;
	}
	/**
	 * sets the associated chart(JFreeChart) with the given chart(JFreeChart)
	 * @param chart
	 */
	public void setChart(JFreeChart chart) {
		this.chart = chart;
	}
	/**
	 * returns the associated drawColor(Color)
	 * @return
	 */
	public Color getDrawColor() {
		return drawColor;
	}
	/**
	 * sets the associated drawColor(Color) with the given drawColor(Color)
	 * @param drawColor
	 */
	public void setDrawColor(Color drawColor) {
		this.drawColor = drawColor;
	}

}
